package cn.com.bluemoon.daps.model.core.file.parser;

import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.toolkit.DbTypeHelper;
import cn.com.bluemoon.daps.common.enums.DatabaseType;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.model.core.file.*;
import cn.com.bluemoon.daps.model.core.file.excel.listener.*;
import cn.com.bluemoon.daps.model.core.file.hive.HiveFieldData;
import cn.com.bluemoon.daps.model.core.file.hive.HivePartitionData;
import cn.com.bluemoon.daps.model.core.file.hive.HiveTableData;
import cn.com.bluemoon.daps.model.core.file.mysql.MySqlFieldData;
import cn.com.bluemoon.daps.model.core.file.mysql.MySqlIndexData;
import cn.com.bluemoon.daps.model.core.file.mysql.MySqlTableData;
import cn.com.bluemoon.daps.model.core.file.pgsql.PgSqlFieldData;
import cn.com.bluemoon.daps.model.core.file.pgsql.PgSqlIndexData;
import cn.com.bluemoon.daps.model.core.file.pgsql.PgSqlTableData;
import cn.com.bluemoon.daps.model.core.file.report.ImportBaseInfo;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.poifs.filesystem.FileMagic;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

import static com.alibaba.excel.support.ExcelTypeEnum.XLS;
import static com.alibaba.excel.support.ExcelTypeEnum.XLSX;

/**
 * @author Jarod.Kong
 * @date 2021/3/11 13:58
 */
@Slf4j
public class ExcelFileParser extends AbstractFileParser {

    public ExcelFileParser() {
        super();
    }

    /**
     * copy com.alibaba.excel.support.ExcelTypeEnum#recognitionExcelType(java.io.InputStream)
     *
     * @param inputStream com.alibaba.excel.support.ExcelTypeEnum#recognitionExcelType(java.io.InputStream)
     * @return xls/xlsx
     * @throws Exception e
     */
    private static ExcelTypeEnum recognitionExcelType(InputStream inputStream) {
        FileMagic fileMagic = null;
        try (BufferedInputStream stream = new BufferedInputStream(inputStream)) {
            fileMagic = FileMagic.valueOf(stream);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
        if (FileMagic.OLE2.equals(fileMagic)) {
            return XLS;
        }
        if (FileMagic.OOXML.equals(fileMagic)) {
            return XLSX;
        }
        return null;
    }

    /**
     * @param file 文件
     * @return xls/xlsx
     */
    private static ExcelTypeEnum valueOfFile(File file) {
        // Use the name to determine the type
        String fileName = file.getName();
        if (fileName.endsWith(XLSX.getValue())) {
            return XLSX;
        } else if (fileName.endsWith(XLS.getValue())) {
            return XLS;
        }
        return null;
    }


    /**
     * 获取文件类型
     *
     * @param file 获取文件类型
     * @return 获取文件类型
     * @throws FileNotFoundException
     */
    public static ExcelTypeEnum getExcelTypeEnum(File file) throws FileNotFoundException {
        ExcelTypeEnum excelTypeEnum = recognitionExcelType(new FileInputStream(file));
        if (excelTypeEnum == null) {
            excelTypeEnum = valueOfFile(file);
        }
        return excelTypeEnum;
    }

    /**
     * 解析+内部校验
     *
     * @param file 文件
     * @return null+parserResult#FAIL 表示解析错误/解析结果校验失败！ List表示通过校验+解析，可能为空（没有数据）
     */
    @SuppressWarnings("rawtypes")
    @Override
    public ResultBean<List<TableInfoDto>> parser(File file) {
        final DatabaseType dbType = DbTypeHelper.getDatabaseType(getGlobalParserConfig().getDbType());
        BaseDataListener<TableData> tableDataListener = new TableDataListener(getGlobalParserConfig());
        BaseDataListener<FieldData> fieldDataListener = new FieldDataListener(getGlobalParserConfig());
        BaseDataListener<IndexData> indexDataListener = new IndexDataListener(getGlobalParserConfig());
        BaseDataListener<PartitionData> partitionDataListener = new PartitionDataListener(getGlobalParserConfig());
        // 定义解析
        Map<DatabaseType, List<Tuple2<Class<? extends IMetaData>, BaseDataListener<? extends IMetaData>>>> readInfos = Maps.newHashMap();
        readInfos.put(DatabaseType.MYSQL, Arrays.asList(Tuples.of(MySqlTableData.class, tableDataListener),
                Tuples.of(MySqlFieldData.class, fieldDataListener), Tuples.of(MySqlIndexData.class, indexDataListener)));
        readInfos.put(DatabaseType.HIVE, Arrays.asList(Tuples.of(HiveTableData.class, tableDataListener),
                Tuples.of(HiveFieldData.class, fieldDataListener), Tuples.of(HivePartitionData.class, partitionDataListener)));
        readInfos.put(DatabaseType.PGSQL, Arrays.asList(Tuples.of(PgSqlTableData.class, tableDataListener),
                Tuples.of(PgSqlFieldData.class, fieldDataListener), Tuples.of(PgSqlIndexData.class, indexDataListener)));
        readInfos.put(DatabaseType.POSTGRESQL, Arrays.asList(Tuples.of(PgSqlTableData.class, tableDataListener),
                Tuples.of(PgSqlFieldData.class, fieldDataListener), Tuples.of(PgSqlIndexData.class, indexDataListener)));
        List<Tuple2<Class<? extends IMetaData>, BaseDataListener<? extends IMetaData>>> readStrategy = readInfos.get(dbType);
        BmAssetUtils.isTrue(readStrategy != null && !readStrategy.isEmpty(), "当前不支持该数据库类型：" + dbType);
        ExcelReader excelReader = null;
        try {
            // 增加判断excel文件类型
            ExcelTypeEnum excelTypeEnum = getExcelTypeEnum(file);
            excelReader = EasyExcel.read(file).excelType(excelTypeEnum).build();
            final int headRowNumber = 1;
            List<ReadSheet> readSheets = Lists.newArrayList();
            for (int i = 0; i < readStrategy.size(); i++) {
                Tuple2<Class<? extends IMetaData>, BaseDataListener<?>> sheet = readStrategy.get(i);
                Class<?> t1 = sheet.getT1();
                BaseDataListener<? extends IMetaData> listener = sheet.getT2();
                ReadSheet readSheet = EasyExcel.readSheet(i).head(t1).headRowNumber(headRowNumber)
                        .registerReadListener(listener).build();
                readSheets.add(readSheet);
            }
            // 这里注意 一定要把sheet1 sheet2 一起传进去，不然有个问题就是03版的excel 会读取多次，浪费性能
            excelReader.read(readSheets);
        } catch (IOException e) {
            log.error("文件解析失败", e);
            return ResultBean.error("文件解析失败，请检查导入文件是否符合！");
        } finally {
            if (excelReader != null) {
                // 这里千万别忘记关闭，读的时候会创建临时文件，到时磁盘会崩的
                excelReader.finish();
            }
        }
        // 检查是否存在不合法
        // 有序内存-结果集
        Map<String, List<TableData>> tableDataMap = tableDataListener.getDataMap();
        Map<String, List<FieldData>> fieldDataMap = fieldDataListener.getDataMap();
        Map<String, List<IndexData>> indexDataMap = indexDataListener.getDataMap();
        Map<String, List<PartitionData>> partitionDataMap = partitionDataListener.getDataMap();
        // preMergeHandler 处理字段、索引、分区中是否存在表英文名在表Sheet中找不到的
        checkSheetRelateIssues(fieldDataListener.getNoValidCollector(), tableDataMap, fieldDataMap);
        checkSheetRelateIssues(indexDataListener.getNoValidCollector(), tableDataMap, indexDataMap);
        checkSheetRelateIssues(partitionDataListener.getNoValidCollector(), tableDataMap, partitionDataMap);
        // 组合异常
        List<ImportBaseInfo> baseInfos = getImportBaseInfos(tableDataListener, fieldDataListener, indexDataListener, partitionDataListener);
        if (!baseInfos.isEmpty()) {
            ResultBean<List<TableInfoDto>> error = ResultBean.error("文件中存在不合法问题，终止导入！");
            error.addMoreData(PARSER_VALiD_ERROR_KEY, baseInfos);
            return error;
        }
        // 最终整合表+字段+索引+分区信息为List<TableInfoDto>
        List<TableInfoDto> tableInfoDtos = tableDataMap.entrySet().stream()
                .map(entry -> {
                    String tableName = entry.getKey();
                    List<TableData> tableDatas = entry.getValue();
                    if (tableDatas != null && !tableDatas.isEmpty()) {
                        TableData td = tableDatas.get(0);
                        return TableInfoDto.builder().dbType(dbType).tableData(td)
                                .fieldDatas(fieldDataMap.getOrDefault(tableName, Collections.emptyList()))
                                .indexDatas(indexDataMap.getOrDefault(tableName, Collections.emptyList()))
                                .partitionDatas(partitionDataMap.getOrDefault(tableName, Collections.emptyList()))
                                .build();
                    }
                    return null;
                })
                .filter(Objects::nonNull)
                .collect(Collectors.toList());
        return ResultBean.ok(tableInfoDtos);
    }

    /**
     * 检查非表Sheet中是否存在无法对应的表英文名的情况
     *
     * @param novalidCollector sheet收集错误信息
     * @param tableDataMap 表信息
     * @param dM sheet结果集
     * @param <T>
     */
    @SuppressWarnings("unchecked")
    private <T extends IMetaData> void checkSheetRelateIssues(List<T> novalidCollector,
                                                              Map<String, List<TableData>> tableDataMap,
                                                              Map<String, List<T>> dM) {
        if (novalidCollector == null) {
            return;
        }
        dM.entrySet().stream().filter(kv -> !tableDataMap.containsKey(kv.getKey()))
                .filter(f -> !f.getValue().isEmpty())
                .forEach(f -> {
                    for (T e : f.getValue()) {
                        if (e != null) {
                            e.setFail(true);
                            e.getErrorMsgs().add(e.sheetName() + "Sheet的表英文名在表Sheet中无法找到");
                            log.error("{}Sheet的表英文名{}在表Sheet中无法找到！！", e.sheetName(), e.getTableName());
                            novalidCollector.add(e);
                        }
                    }
                });
    }

    private List<ImportBaseInfo> getImportBaseInfos(BaseDataListener<TableData> tableDataListener, BaseDataListener<FieldData> fieldDataListener, BaseDataListener<IndexData> indexDataListener, BaseDataListener<PartitionData> partitionDataListener) {
        List<ImportBaseInfo> baseInfos = Lists.newArrayList();
        // table
        List<TableData> tableData = tableDataListener.getNoValidCollector();
        if (!tableData.isEmpty()) {
            baseInfos.add(getImportBaseInfo(fieldDataListener.getRowSize(), tableData, "表", 1));
        }
        // field
        List<FieldData> fieldData = fieldDataListener.getNoValidCollector();
        if (!fieldData.isEmpty()) {
            baseInfos.add(getImportBaseInfo(fieldDataListener.getRowSize(), fieldData, "字段", 2));
        }
        // index
        List<IndexData> indexData = indexDataListener.getNoValidCollector();
        if (!indexData.isEmpty()) {
            baseInfos.add(getImportBaseInfo(indexDataListener.getRowSize(), indexData, "索引", 3));
        }
        // partition
        List<PartitionData> partitionData = partitionDataListener.getNoValidCollector();
        if (!partitionData.isEmpty()) {
            baseInfos.add(getImportBaseInfo(partitionDataListener.getRowSize(), partitionData, "分区", 3));
        }
        return baseInfos;
    }
}
